package com.easywork.jythonspring;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Hashtable;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.python.core.PyObject;
import org.python.util.PythonInterpreter;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;

public class PythonHandlerMapping extends AbstractHandlerMapping implements InitializingBean{
	private static final Logger logger = Logger.getLogger(PythonHandlerMapping.class);
	/**
	 * Default file name to handle request under webapp folder
	 */
	private static final String DEFAULT_FILE_NAME = "default";
	
	private static final String PYTHON_FILE_SUFFIX = ".py";
	/**
	 * Folder that will contains all python files
	 */
	private String pythonPackage;
	/**
	 * Python Home folder for cache storage
	 */
	private String pythonHome;
	
	/**
	 * Extension package for jython runtime environment
	 */
	private List<String> addPackages;
	/**
	 * Extension class path for jython runtime environment
	 */
	private List<String> addClassDirs;
	/**
	 * Extension jar or zip libs dir for jython runtime environment
	 */
	private List<String> addExtDirs;
	
	/**
	 * Server side cache for speed up
	 */
	private Hashtable<String,CacheEntry> cache;
	/**
	 * Whether to use one python file to handle one request, default is false
	 */
	private boolean oneRequestOneFile = true;
	/**
	 * Default python handler name for request under the base webapp folder.
	 * Only affected when oneRequestOneFile = false
	 */
	private String defaultFileName;
	
	public void setOneRequestOneFile(boolean oneRequestOneFile) {
		this.oneRequestOneFile = oneRequestOneFile;
	}

	@Override
	protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
		//get uri under the context path
		String requestName = request.getRequestURI().replaceFirst(request.getContextPath(), "");
		int lastDot = requestName.indexOf(".");
		
		if(lastDot > -1)
			//remove the suffix
			requestName = requestName.substring(0,lastDot);
		
		//handler name
		String[] parts = requestName.split("/");
		String handlerName = parts[parts.length-1];		//last one is our handler's name
		String fileName = pythonPackage;
		if(oneRequestOneFile)
			fileName =  fileName + requestName + PYTHON_FILE_SUFFIX;
		else
			if(parts.length == 2)
				//visiting links in webapp root
				fileName = fileName + File.separatorChar + defaultFileName + PYTHON_FILE_SUFFIX;
			else
			{
				//handle the path
				for(int i=0;i<parts.length-2;i++)
					if(StringUtils.isNotBlank(parts[i]) && i+2 < parts.length)
						fileName = fileName + File.separatorChar + parts[i];
				fileName = fileName + File.separatorChar + parts[parts.length - 2] + PYTHON_FILE_SUFFIX;	//the second last is the file name
			}
		PyObject handler = null;
		try
		{
			File file = new File(fileName);
			if(!file.exists())
				try
				{
					file = new File(PythonHandlerMapping.class.getResource(fileName).toURI());	
				}catch(NullPointerException ex)
				{
					throw new FileNotFoundException(fileName+" not found for request "+requestName);
				}
				
			CacheEntry cached = cache.get(request.getRequestURI()+"|METHOD="+request.getMethod());
			
			if(cached != null && cached.lastModified == file.lastModified())
				handler = cached.handler;
			else
			{
				if(!PythonInitializer.isInitialized())
				{
					initPythonIntercepter();
				}
				PythonInterpreter python = new PythonInterpreter();
				python.execfile(file.getAbsolutePath());
				handler = python.get(handlerName);
				if(handler == null)
				{
					handlerName = handlerName + request.getMethod();
					handler = python.get(handlerName);
					if(handler == null)
						throw new NoSuchMethodError(fileName+" does not have method ("+handlerName+") for request "+requestName);
				}
				
				
				//put it to cahce pool
				CacheEntry aCache = null;
				if(cached != null)
					aCache = cached;
				else
					aCache = new CacheEntry(handler,file.lastModified());
				cache.put(request.getRequestURI()+"|METHOD="+request.getMethod(), aCache);
			}
		}catch (Exception e) {
			logger.warn("No handler file for request url "+request.getRequestURI(),e);
			throw e;
		}
		return handler;
	}
	
	private void initPythonIntercepter() throws Exception {
		PythonInitializer init = new PythonInitializer();
		init.setAddPackages(addPackages);
		init.setAddClassDirs(addClassDirs);
		init.setAddExtDirs(addExtDirs);
		init.setPythonHome(pythonHome);
		init.afterPropertiesSet();
		
		//adding servlet api
//		PySystemState.add_package("javax.servlet");
//		PySystemState.add_package("javax.servlet.http");
//		PySystemState.add_package("javax.servlet.jsp");
//		PySystemState.add_package("javax.servlet.jsp.tagext");
	}

	/**
	 * Clear cache
	 */
	public void clearCache()
	{
		cache.clear();
	}

	/**
	 * We give initial value for the properties here and set up the Python environment
	 */
	public void afterPropertiesSet() throws Exception {
		if(!oneRequestOneFile && StringUtils.isBlank(defaultFileName))
			defaultFileName = DEFAULT_FILE_NAME;
		//give pythonPackage a default value, all under python folder in classpath
		if(StringUtils.isBlank(pythonPackage))
			pythonPackage = "/python";
		cache = new Hashtable<String, CacheEntry>();
	}

	public void setPythonPackage(String pythonPackage) {
		this.pythonPackage = pythonPackage;
	}

	public void setPythonHome(String pythonHome) {
		this.pythonHome = pythonHome;
	}

	public void setDefaultFileName(String defaultFileName) {
		this.defaultFileName = defaultFileName;
	}

	public void setAddPackages(List<String> addPackage) {
		this.addPackages = addPackage;
	}

	public void setAddClassDirs(List<String> addClassDir) {
		this.addClassDirs = addClassDir;
	}

	public void setAddExtDirs(List<String> addExtDir) {
		this.addExtDirs = addExtDir;
	}
}
